package com.hyq.learn.api.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.io.SocketConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.*;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author heyuqin
 * @date 2024/5/29 11:47
 */
@Configuration
public class RestClientConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(RestClientConfig.class);
    private static final Logger REMOTE_LOGGER = LoggerFactory.getLogger("REMOTE_LOGGER");

    @Autowired
    private ObjectMapper objectMapper;

    @Bean
    public RestTemplate commonRestTemplate(ApplicationProperties applicationProperties) {
        LOGGER.info("Creating commonRestTemplate...");

        final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(applicationProperties.getHttp().getReadTimeout(), TimeUnit.MILLISECONDS).setTcpNoDelay(true).build());
        cm.setDefaultMaxPerRoute(applicationProperties.getHttp().getMaxPerRoute());
        cm.setMaxTotal(applicationProperties.getHttp().getMaxTotal());

        HttpClient httpClient = HttpClientBuilder.create().evictExpiredConnections().setConnectionManager(cm).build();

        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
        factory.setConnectTimeout(applicationProperties.getHttp().getConnectTimeout());
        factory.setConnectionRequestTimeout(applicationProperties.getHttp().getConnectionRequestTimeout());

        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.getMessageConverters().removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
        restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter(objectMapper));
        restTemplate.setErrorHandler(new AcceptResponseErrorHandler());

        LOGGER.debug("Registering interceptor for RestTemplate request");
        List<ClientHttpRequestInterceptor> requestInterceptors = new ArrayList<>(restTemplate.getInterceptors());
        requestInterceptors.add(new CustomRequestInterceptor());
        restTemplate.setInterceptors(requestInterceptors);

        return restTemplate;
    }

    private static class AcceptResponseErrorHandler implements ResponseErrorHandler {

        @Override
        public boolean hasError(ClientHttpResponse response) throws IOException {
            return false;
        }

        @Override
        public void handleError(ClientHttpResponse response) throws IOException {

        }
    }

    private static class CustomRequestInterceptor implements ClientHttpRequestInterceptor {
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            long start = System.currentTimeMillis();
            String requestBody = "N/A";
            if (body != null && body.length > 0) {
                requestBody = new String(body, Charset.defaultCharset());
            }

            String responseBody = "N/A";
            HttpStatusCode statusCode = null;
            ClientHttpResponse response;
            try {
                response = execution.execute(request, body);
                final ClientHttpResponse responseCopy = new BufferingClientHttpResponseWrapper(response);
                statusCode = response.getStatusCode();
                responseBody = getResponseBodyAsString(responseCopy);
                return responseCopy;
            } finally {
                long end = System.currentTimeMillis();
                REMOTE_LOGGER.info("{}||{}||{}||{}||{}||{}", request.getMethod(), request.getURI(), requestBody, statusCode != null ? statusCode.value() : 0, responseBody, end - start);
            }
        }

        private String getResponseBodyAsString(ClientHttpResponse response) {
            byte[] payload = getResponseBody(response);
            if (payload == null || payload.length == 0) {
                return null;
            }

            Charset charset = getCharset(response);
            if (charset == null) {
                charset = Charset.defaultCharset();
            }

            return new String(payload, charset);
        }

        protected Charset getCharset(ClientHttpResponse response) {
            HttpHeaders headers = response.getHeaders();
            MediaType contentType = headers.getContentType();
            return contentType != null ? contentType.getCharset() : null;
        }

        private byte[] getResponseBody(ClientHttpResponse response) {
            try {
                return FileCopyUtils.copyToByteArray(response.getBody());
            } catch (IOException ex) {
                LOGGER.error("Unexpected error occurs while reading response body", ex);
                return null;
            }
        }

    }

    private static class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
        private final ClientHttpResponse response;

        private byte[] body;

        BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
            this.response = response;
        }

        @Override
        public HttpStatusCode getStatusCode() throws IOException {
            return this.response.getStatusCode();
        }

        @Override
        public int getRawStatusCode() throws IOException {
            return this.response.getRawStatusCode();
        }

        @Override
        public String getStatusText() throws IOException {
            return this.response.getStatusText();
        }

        @Override
        public HttpHeaders getHeaders() {
            return this.response.getHeaders();
        }

        @Override
        public InputStream getBody() throws IOException {
            if (this.body == null) {
                this.body = StreamUtils.copyToByteArray(this.response.getBody());
            }
            return new ByteArrayInputStream(this.body);
        }

        @Override
        public void close() {
            this.response.close();
        }
    }
}
